
package org.apache.ocean;

import java.util.*;
import java.util.regex.*;

/**
 *
 * @author  jason rutherglen
 */
public class CronType {
  private static final long DAY = 24L * 3600L * 1000L;
  
  private final Calendar _localCalendar = Calendar.getInstance();
  
  private boolean []_minutes;
  private boolean []_hours;
  private boolean []_days;
  private boolean []_months;
  private boolean []_daysOfWeek;
  
  public static void main(String[] args) {
    try {
      CronType cronType = new CronType();
      //cronType.addText("*/3"); 
      //cronType.addText("0 */3");
      cronType.addText("*/10");
      
      Date nowDate = new Date();
      System.out.println("nowDate: "+nowDate);
      long nextTime = cronType.nextTime(nowDate.getTime());
      Date date = new Date(nextTime);
      System.out.println("nexttime: "+date);
    } catch (Throwable ex) {
      ex.printStackTrace();
    }
  }
  
  public CronType() {
  }
  
  /**
   * Sets the text.
   */
  public void addText(String text) throws Exception {
    text = text.trim();
    
    String[] split = Pattern.compile("\\s+").split(text);
    
    if (split.length > 0)
      _minutes = parseRange(split[0], 0, 59);
    
    if (split.length > 1)
      _hours = parseRange(split[1], 0, 23);
    else
      _hours = parseRange("*", 0, 23);
    
    if (split.length > 2)
      _days = parseRange(split[2], 1, 31);
    
    if (split.length > 3)
      _months = parseRange(split[3], 1, 12);
    
    if (split.length > 4)
      _daysOfWeek = parseRange(split[4], 0, 7);
  }
  
  /**
   * parses a range, following cron rules.
   */
  public static boolean[] parseRange(String range, int rangeMin, int rangeMax) throws Exception {
    boolean[] values = new boolean[rangeMax + 1];
    
    int j = 0;
    while (j < range.length()) {
      char ch = range.charAt(j);
      
      int min = 0;
      int max = 0;
      int step = 1;
      
      if (ch == '*') {
        min = rangeMin;
        max = rangeMax;
        j++;
      }
      else if ('0' <= ch && ch <= '9') {
        for (;
        j < range.length() && '0' <= (ch = range.charAt(j)) && ch <= '9';
        j++) {
          min = 10 * min + ch - '0';
        }
        
        if (j < range.length() && ch == '-') {
          for (j++;
          j < range.length() && '0' <= (ch = range.charAt(j)) && ch <= '9';
          j++) {
            max = 10 * max + ch - '0';
          }
        }
        else
          max = min;
      }
      else
        throw new Exception("is an illegal cron range: "+range);
      
      if (min < rangeMin)
        throw new Exception("is an illegal cron range (min value is too small)");
      else if (rangeMax < max)
        throw new Exception("is an illegal cron range (max value is too large)");
      
      if (j < range.length() && (ch = range.charAt(j)) == '/') {
        step = 0;
        
        for (j++;
        j < range.length() && '0' <= (ch = range.charAt(j)) && ch <= '9';
        j++) {
          step = 10 * step + ch - '0';
        }
        
        if (step == 0)
          throw new Exception("is an illegal cron range");
      }
      
      if (range.length() <= j) {
      } else if (ch == ',')
        j++;
      else {
        throw new Exception("is an illegal cron range");
      }
      
      for (; min <= max; min += step)
        values[min] = true;
    }
    
    return values;
  }
  
  public long nextTime(long now) {
    Calendar cal = _localCalendar;
    
    long time = now + 60000 - now % 60000;
    
    synchronized (cal) {
      cal.setTimeInMillis(time);
      
      int minute = nextInterval(_minutes, cal.get(Calendar.MINUTE));
      
      if (minute < 0) {
        minute = nextInterval(_minutes, 0);
        
        cal.add(Calendar.HOUR_OF_DAY, 1);
      }
      
      int hour = nextInterval(_hours, cal.get(Calendar.HOUR_OF_DAY));
      if (hour < 0) {
        hour = nextInterval(_hours, 0);
        minute = nextInterval(_minutes, 0);
        
        cal.add(Calendar.DAY_OF_MONTH, 1);
      }
      
      int day = cal.get(Calendar.DAY_OF_MONTH);
      
      if (_days != null) {
        day = nextInterval(_days, cal.get(Calendar.DAY_OF_MONTH));
        
        if (day < 0) {
          cal.add(Calendar.MONTH, 1);
          cal.set(Calendar.DAY_OF_MONTH, 1);
          //cal.setMonth(cal.getMonth() + 1);
          // cal.setDayOfMonth(1);
          
          day = nextInterval(_days, cal.get(Calendar.DAY_OF_MONTH));
          hour = nextInterval(_hours, 0);
          minute = nextInterval(_minutes, 0);
        }
      }
      
      if (_daysOfWeek != null) {
        int oldDayOfWeek = cal.get(Calendar.DAY_OF_WEEK);//cal.getDayOfWeek();
        int dayOfWeek = nextInterval(_daysOfWeek, oldDayOfWeek);
        
        if (dayOfWeek > 0) {
          day += (dayOfWeek - oldDayOfWeek);
        }
        else {
          dayOfWeek = nextInterval(_daysOfWeek, 0);
          
          day += (dayOfWeek - oldDayOfWeek + 7);
        }
      }
      
      int month = cal.get(Calendar.MONTH);
      int year = (int)cal.get(Calendar.YEAR);
      
      long nextTime = nextTime(year, month, day, hour, minute);
      
      if (now < nextTime)
        return nextTime;
      else
        return nextTime(now + 3600000L); // DST
    }
  }
  
  private long nextTime(int year, int month, int day, int hour, int minute) {
    Calendar cal = _localCalendar;
    
    //cal.setLocalTime(0);
    cal.setTimeInMillis(0);
    
    cal.set(Calendar.YEAR, year);
    cal.set(Calendar.MONTH, month);
    cal.set(Calendar.DAY_OF_MONTH, day);
    cal.set(Calendar.HOUR_OF_DAY, hour);
    cal.set(Calendar.MINUTE, minute);
    
    return cal.getTimeInMillis();
  }
  
  public int nextInterval(boolean[] values, int now) {
    for (; now < values.length; now++) {
      if (values[now])
        return now;
    }
    
    return -1;
  }
}